#include "my_inet.h"
#include "my_str.h"

static inline my_bool_t
my_address_port_parser(const char *start, const char *end, int *port)
{
    int tport = 0;
    while (start != end) {
        if (*(start) == ':') {
            start++;
            continue;
        }

        if (*start < '0' || *start > '9') {
            return MY_FALSE;
        }
        tport = tport * 10 + (*start - '0');
        start++;
    }

    *port = tport;
    return MY_TRUE;
}

my_bool_t
my_address_parser(const char *addr_str, my_address_t *address, int *port)
{
    size_t addr_len = strlen(addr_str);

    // it's a unix domain address
    if (addr_len > MY_UNIX_ADDRESS_PREFIX_LEN &&
        strncmp(addr_str, MY_UNIX_ADDRESS_PREFIX, MY_UNIX_ADDRESS_PREFIX_LEN) == 0) {
        if (addr_len - MY_UNIX_ADDRESS_PREFIX_LEN >
            sizeof(struct sockaddr_un) - offsetof(struct sockaddr_un, sun_path) - 1) {
            return MY_FALSE;
        }
        address->sockaddr_un.sun_family = AF_UNIX;
        my_copy(address->sockaddr_un.sun_path, addr_str + MY_UNIX_ADDRESS_PREFIX_LEN,
            (int64_t)(addr_len - MY_UNIX_ADDRESS_PREFIX_LEN));
        address->sockaddr_un.sun_path[addr_len - MY_UNIX_ADDRESS_PREFIX_LEN] = '\0';
        return MY_TRUE;
    }

    int i;
    int tport = 0;
    int addr_end_pos = 0;
    char tmp_addr[MY_INET6_ADDRSTRLEN];

    // it's a ipv6 address
    if (addr_len > 1 && *addr_str == '[') {
        for (i = 1; i < (int64_t)addr_len; i++) {
            if (*(addr_str + i) == ']') {
                addr_end_pos = i;
                break;
            }
        }

        if (addr_end_pos == 0) {
            return MY_FALSE;
        }

        addr_end_pos += 1;
        if (addr_end_pos != (int64_t)addr_len &&
            !my_address_port_parser(addr_str + addr_end_pos, addr_str + addr_len, &tport)) {
            return MY_FALSE;
        }

        if (port != NULL) {
            *port = tport;
        }
        
        address->sockaddr_in6.sin6_family = AF_INET6;
        address->sockaddr_in6.sin6_port = htons((uint16_t)tport);
        my_copy(tmp_addr, addr_str + 1, addr_end_pos - 2); // skip '[' and ']'
        int res = inet_pton(AF_INET6, tmp_addr, &address->sockaddr_in6.sin6_addr);
        if (res != 1) {
            return MY_FALSE;
        }

        return MY_TRUE;
    }

    // it's a ipv4 address
    addr_end_pos = 0;
    for (i = 0; i < (int64_t)addr_len; i++) {
        if (*(addr_str+ i) != ':') {
            addr_end_pos++;
            continue;
        }

        break;
    }

    if (addr_end_pos != (int64_t)addr_len &&
        !my_address_port_parser(addr_str + addr_end_pos, addr_str + addr_len, &tport)) {
        return MY_FALSE;
    }

    if (port != NULL) {
        *port = tport;
    }

    address->sockaddr_in.sin_family = AF_INET;
    address->sockaddr_in.sin_port = htons((uint16_t)tport);

    int res;
    if (addr_end_pos != 0) {
        my_copy(tmp_addr, addr_str, addr_end_pos);
        tmp_addr[addr_end_pos] = '\0';
        res = inet_pton(AF_INET, tmp_addr, &address->sockaddr_in.sin_addr);
    } else {
        res = inet_pton(AF_INET, tmp_addr, &address->sockaddr_in.sin_addr);
    }

    if (res != 1) {
        return MY_FALSE;
    }

    return MY_TRUE;
}

my_bool_t
my_address_init_by_sockaddr(my_address_t *address, my_sockaddr_t *sockaddr)
{
    if (sockaddr->sa_family == AF_UNIX) {
        my_copy((char *)&address->sockaddr_un, (char *)sockaddr, sizeof(my_sockaddr_un_t));
        return MY_TRUE;
    }

    if (sockaddr->sa_family == AF_INET) {
        my_copy((char *)&address->sockaddr_in, (char *)sockaddr, sizeof(my_sockaddr_in_t));
        return MY_TRUE;
    }

    if (sockaddr->sa_family == AF_INET6) {
        my_copy((char *)&address->sockaddr_in6, (char *)sockaddr, sizeof(my_sockaddr_in6_t));
        return MY_TRUE;
    }

    return MY_FALSE;
}

int64_t
my_address_to_str(my_address_t *address, char *dest, int dest_size)
{
    return my_sockaddr_to_str(&address->sockaddr, dest, dest_size);
}

int64_t
my_sockaddr_to_str(my_sockaddr_t *addr, char *dest, int dest_size) {
    if (addr->sa_family == AF_UNIX) {
        my_sockaddr_un_t *sockaddr_un = (my_sockaddr_un_t *)addr;
        if (my_strlen_try(sockaddr_un->sun_path,
            sizeof(my_sockaddr_un_t) - offsetof(my_sockaddr_un_t, sun_path)) == -1) {
            return 0;
        }

        return snprintf(dest, (size_t)dest_size, "%s%s", MY_UNIX_ADDRESS_PREFIX, sockaddr_un->sun_path);
    }

    int64_t len;
    const char *res;

    if (addr->sa_family == AF_INET) {
        my_sockaddr_in_t *sockaddr_in = (my_sockaddr_in_t *)addr;
        res = inet_ntop(AF_INET, &sockaddr_in->sin_addr, dest, (socklen_t)dest_size);
        if (!res) {
            return 0;
        }

        len = (int64_t)strlen(dest);
        int64_t m = snprintf(dest + len, (size_t)(dest_size - len), ":%d", ntohs(sockaddr_in->sin_port));
        return len + m;
    }

    if (addr->sa_family == AF_INET6) {
        my_sockaddr_in6_t *sockaddr_in6 = (my_sockaddr_in6_t *)addr;
        *dest = '[';
        res = inet_ntop(AF_INET6, &sockaddr_in6->sin6_addr, dest+1, (socklen_t)(dest_size - 1));
        if (!res) {
            return 0;
        }

        len = (int64_t)strlen(dest);
        int64_t m = snprintf(dest + len, (size_t)(dest_size - len), "]:%d", ntohs(sockaddr_in6->sin6_port));
        return len + m;
    }

    return 0;
}